package com.ahncnk.powermonitor.admin.auth.interceptor;

import com.ahncnk.powermonitor.admin.AppConfig;
import com.ahncnk.powermonitor.admin.auth.domain.dto.JwtVerifyResult;
import com.ahncnk.powermonitor.admin.auth.domain.dto.Token;
import com.ahncnk.powermonitor.admin.auth.service.AuthService;
import com.ahncnk.powermonitor.admin.auth.service.UserContext;
import com.ahncnk.powermonitor.commons.exception.ApplicationRuntimeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

/**
 * jwt双Cookie认证实现.
 *
 * @author lipei
 */
@Component
public class AuthInterceptor implements HandlerInterceptor {
    private static final String BEARER_PREFIX = "Bearer ";

    private final AuthService authService;
    private final boolean allowCsrf;

    public AuthInterceptor(AuthService authService, AppConfig appConfig) {
        this.authService = authService;
        this.allowCsrf = appConfig.getAuth().isAllowCsrf();
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String jwt = allowCsrf ?
                extractJwtFromCookie(request) :
                extractJwtFromHeaderAndCookie(request);
        try {
            JwtVerifyResult jwtVerifyResult = authService.verify(jwt);
            UserContext.setCurrent(jwtVerifyResult.getUserPrincipal());

            Instant issuedTime = jwtVerifyResult.getDecodedJWT().getIssuedAt().toInstant();
            if (issuedTime.until(Instant.now(), ChronoUnit.SECONDS) > 30) {
                Token token = authService.sign(jwtVerifyResult.getUserPrincipal());
                response.addCookie(token.getPayload());
                response.addCookie(token.getSignature());
            }
            return true;
        } catch (JWTVerificationException ex) {
            throw new ApplicationRuntimeException(HttpStatus.UNAUTHORIZED, "验证jwt失败", "会话过期，请重新登录……", ex);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.removeCurrent();
    }


    /**
     * 从cookie中解析出jwt.
     *
     * <p>只校验cookie会存在<strong>csrf漏洞</strong>，使用 {@link #extractJwtFromHeaderAndCookie(HttpServletRequest)} 代替。</p>
     */
    private String extractJwtFromCookie(HttpServletRequest request) {
        String headerAndPayload = null, signature = null;
        for (Cookie cookie : request.getCookies()) {
            if (authService.getHeaderAndPayloadCookieName().equals(cookie.getName())) {
                headerAndPayload = cookie.getValue();
                if (signature != null) {
                    break;
                }
            }
            if (authService.getSignatureCookieName().equals(cookie.getName())) {
                signature = cookie.getValue();
                if (headerAndPayload != null) {
                    break;
                }
            }
        }
        return headerAndPayload + "." + signature;
    }

    /**
     * 从Header中解析出 header.payload 部分，从Cookie中解析出 signature 部分，合并成jwt.
     */
    private String extractJwtFromHeaderAndCookie(HttpServletRequest request) {
        String headerAndPayload = extractAuthenticationFromHeader(request);
        String signature = extractSignatureFromCookie(request);
        return headerAndPayload + "." + signature;
    }

    private String extractAuthenticationFromHeader(HttpServletRequest request) {
        String authenticationHeader = request.getHeader("Authorization");
        if (StringUtils.isEmpty(authenticationHeader)) {
            throw new ApplicationRuntimeException(HttpStatus.UNAUTHORIZED, "请求头中未包含Authorization", "请登录后重试");
        }
        if (!authenticationHeader.startsWith(BEARER_PREFIX)) {
            throw new ApplicationRuntimeException(HttpStatus.UNAUTHORIZED, "不支持的认证模式：\"" + authenticationHeader + "\"，当前仅支持 Bearer 认证。", "请登录后重试");
        }
        String authentication = authenticationHeader.substring(BEARER_PREFIX.length());
        if (StringUtils.isEmpty(authentication)) {
            throw new ApplicationRuntimeException(HttpStatus.UNAUTHORIZED, "缺少认证信息", "请登录后重试");
        }
        return authentication;
    }

    private String extractSignatureFromCookie(HttpServletRequest request) {
        for (Cookie cookie : request.getCookies()) {
            if (authService.getSignatureCookieName().equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
        return "";
    }


}
